package org.xtremeobjects.forms.ui.editors;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.naming.OperationNotSupportedException;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeansObservables;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.nebula.widgets.cdatetime.CDT;
import org.eclipse.nebula.widgets.cdatetime.CDateTime;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
import org.eclipse.ui.part.EditorPart;
import org.hibernate.criterion.PropertySubqueryExpression;
import org.xtremeobjects.domain.aspects.javabean.PropertySupport;
import org.xtremeobjects.domain.core.di.Injector;
import org.xtremeobjects.domain.model.Association;
import org.xtremeobjects.domain.model.Clazz;
import org.xtremeobjects.domain.model.ClazzFactoryImpl;
import org.xtremeobjects.domain.model.ClazzRepository;
import org.xtremeobjects.domain.model.Operation;
import org.xtremeobjects.domain.model.Property;
import org.xtremeobjects.domain.model.Association.AssociationType;
import org.xtremeobjects.domain.model.ddd.config.ModuleInfoRepository;
import org.xtremeobjects.forms.ui.XObjectEditorInput;


public class ObjectEditor extends EditorPart {

	public static final String ID = "org.xtremeobjects.workbech.rcp.objecteditor";

	private FormToolkit toolkit;

	private ScrolledForm form;

	private static Realm realm;

	private ObjectEditorModel model;

	private DataBindingContext bindingContext;

	private ClazzRepository clazzRepository = (ClazzRepository) Injector.getInstance(ClazzRepository.class);

	@Override
	public void doSave(IProgressMonitor monitor) {
		// TODO Auto-generated method stub
		try {
			model.save();
		} catch (OperationNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	@Override
	public void doSaveAs() {
		// TODO Auto-generated method stub

	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		setSite(site);
		setInput(input);
	}
	
	public XObjectEditorInput getObjectInput() {
		return (XObjectEditorInput)this.getEditorInput();
	}

	private void createModel() throws Exception {
		ObjectEditorModelFactory factory = (ObjectEditorModelFactory) Injector.getInstance(ObjectEditorModelFactory.class);
		model = factory.create(this.getObjectInput().getClassName(), this.getObjectInput().getObjectID());
	}

	@Override
	public boolean isDirty() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean isSaveAsAllowed() {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public void createPartControl(Composite topParent) {

		try {
			realm = SWTObservables.getRealm(Display.getDefault());

			bindingContext = new DataBindingContext(realm);

			toolkit = new FormToolkit(topParent.getDisplay());
			form = toolkit.createScrolledForm(topParent);
			createModel();
		} catch (Throwable e) {
			form.setText(e.toString());
			e.printStackTrace();
			return;
		} finally {
		}

		// Title
//		Person person = (Person) model.getCurrentObject();
//		form.setText(person.getName());


		/**/
		Composite parent = form.getBody();
		TableWrapLayout layout = new TableWrapLayout();
		parent.setLayout(layout);
//		layout.numColumns = 2;
		createObjectForm(parent, model.getCurrentObject(),null, 0);

//		createButtonBar();
//		createApplyLink(parent);
	}

	private void createObjectForm(Composite parent, Object obj, Object parentObj, int level) {

		Clazz clazz = clazzRepository.findOrCreateByClass(obj.getClass());
		
		if (obj instanceof PropertySupport) {
			PropertySupport propertySupport = (PropertySupport) obj;
			propertySupport.addPropertyChangeListener(new PropertyChangeListener(){ 
				public void propertyChange(PropertyChangeEvent evt) {
					System.out.println("Property " + evt.getPropertyName() + " oldvalue=" + evt.getOldValue() + ", new=" + evt.getNewValue());
				}
			});
			
		}

		Composite form1 = createSection(parent, clazz.getLabel(obj), clazz.getDescription(obj));
		TableWrapLayout layout = new TableWrapLayout();
		form1.setLayout(layout);
		layout.numColumns = 1;

		Composite sectionClient = createComposite(form1, 2);
		createFieldsForProperties(sectionClient, obj);

		Composite sectionClient1 = createComposite(form1, level>0?2:1);
		createSectionForObject(sectionClient1, obj, parentObj, level);

		for (Operation operation : clazz.getPublicOperations()) {
			if(!operation.hasParameters())
				createOperationLink(form1, operation, obj);
		}
	}

	private void createSectionForObject(Composite parent, Object person, Object dad, int level) {
		Clazz clazz = clazzRepository.findOrCreateByClass(person.getClass());

		for (Association association : clazz.getAssociations()) {
			if(association.getAssociationType()==AssociationType.AGREGATION)
			for (Object obj : association.getCollection(person) ) {

				// if circular associations, break the loop
				if(obj.equals(person))
					continue;
				createObjectForm(parent, obj, person,level+1 );
			} else {
				//TODO: Dont show if association does not have nothing
				// maybe show something to add the collection
				if(association.getCollection(person).size()>0)
					createSectionForAssociation(parent, association, person, level+1);					
			}
		}
	}

	private void createSectionForAssociation(Composite parent, Association association, Object obj, int level ) {
		Clazz clazz1 = association.getToClazz();
		Composite sectionClient = createSection(parent, clazz1.getLabel(new String()), "Associations");
		TableWrapLayout layout1 = new TableWrapLayout();
		sectionClient.setLayout(layout1);
		layout1.numColumns = 1;
		CollectionTable table = new CollectionTable(clazz1, new ArrayList(association.getCollection(obj)),sectionClient,getEditorSite().getWorkbenchWindow());
	}

	private void createFieldsForProperties(Composite form2, Object person) {
		List<Property> properties = clazzRepository.findOrCreateByClass(person.getClass()).getProperties();
		for (Property  property: properties ) {

			// Dont show what is hidden
			if(property.isHidden())
				continue;
			
			try {
			if(!property.isReadOnly() && String.class.isAssignableFrom(property.getType().getImplementationClass()))
				createTextField(form2, property.getLabel(), person, property.getName());
			else if(!property.isReadOnly() && Enum.class.isAssignableFrom(property.getType().getImplementationClass()))
				createEnumComboField(form2, property.getLabel(), person, property.getName(), property.getType().getImplementationClass());
			else if(!property.isReadOnly() && Boolean.class.isAssignableFrom(property.getType().getImplementationClass()))
				createBooleanField(form2,property.getLabel(), person, property.getName());
			else if(!property.isReadOnly() && Class.forName("com.domainlanguage.time.CalendarDate").isAssignableFrom(property.getType().getImplementationClass()))
				createDateField(form2, property.getLabel(), person, property.getName());
			else if(!property.isReadOnly() && Integer.class.isAssignableFrom(property.getType().getImplementationClass()))
				createTextField(form2, property.getLabel(), person, property.getName());
			else
				createLabelField(form2, property.getLabel(), person, property.getName());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	private Composite createSection(Composite parent, String title, String description) {
		Section section = toolkit.createSection(parent, Section.DESCRIPTION|ExpandableComposite.TWISTIE|ExpandableComposite.TITLE_BAR|ExpandableComposite.EXPANDED);
		TableWrapLayout layout = new TableWrapLayout();
		section.setLayout(layout);
		layout.numColumns = 1;
//		section.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		section.addExpansionListener(new ExpansionAdapter() {
			@Override
			public void expansionStateChanged(ExpansionEvent e) {
				form.reflow(true);
			}
		});
		section.setText(title); 
		section.setDescription(description); 
		Composite sectionClient = createComposite(section, 1);
		/**/
		return sectionClient;
	}

	private Composite createComposite(Composite section, int col) {
		Composite sectionClient = toolkit.createComposite(section);
		TableWrapLayout layout3 = new TableWrapLayout();
		sectionClient.setLayout(layout3);
		if( section instanceof ExpandableComposite)
			((ExpandableComposite)section).setClient(sectionClient);
		layout3.numColumns = col;
		return sectionClient;
	}

	private void createApplyLink(Composite parent) {
		TableWrapData gd = new TableWrapData();
		gd.colspan = 1;
		Hyperlink link = toolkit.createHyperlink(parent, "Apply",
				SWT.WRAP);
		link.addHyperlinkListener(new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent e) {
				System.out.println("Applied!");
				try {
					model.save();
				} catch (OperationNotSupportedException ex) {
					ex.printStackTrace();
				}
			}
		});
		link.setLayoutData(gd);
	}

	private void createTextField(Composite parent, String fieldLabel, Object fieldObject,
			String property) {
		Label label = toolkit.createLabel(parent, fieldLabel);
		label.setLayoutData(new TableWrapData(TableWrapData.RIGHT));
		Text text = toolkit.createText(parent, "");
		text.setLayoutData(new TableWrapData(TableWrapData.FILL));
		bindingContext.bindValue(SWTObservables.observeText(text, SWT.Modify),
				BeansObservables.observeValue(realm, fieldObject, property),
				null, null);
	}

	private void createLabelField(Composite parent, String fieldLabel, Object fieldObject,
			String property) {
		Label label = toolkit.createLabel(parent, fieldLabel);
		label.setLayoutData(new TableWrapData(TableWrapData.RIGHT));
		Label text = toolkit.createLabel(parent, "");
		text.setLayoutData(new TableWrapData(TableWrapData.FILL));
		bindingContext.bindValue(SWTObservables.observeText(text),
				BeansObservables.observeValue(realm, fieldObject, property),
				null, null);
	}

	private void createBooleanField(Composite parent, String fieldLabel, Object fieldObject,
			String property) {
		Label label = toolkit.createLabel(parent, fieldLabel);
		label.setLayoutData(new TableWrapData(TableWrapData.RIGHT));
		 Button button = toolkit.createButton(parent,
				 "", SWT.CHECK);
		/**/
		bindingContext.bindValue(SWTObservables.observeEnabled(button),
				BeansObservables.observeValue(realm, fieldObject, property),
				null, null);
	}

	private void createEnumComboField(Composite parent, String fieldLabel, Object fieldObject,
			String property, Class enumClazz) {
		if(!enumClazz.isEnum())
			throw new RuntimeException("Not an enum type:" + enumClazz);

		Label label = toolkit.createLabel(parent, fieldLabel);
		label.setLayoutData(new TableWrapData(TableWrapData.RIGHT));
		CCombo combo = new CCombo(parent,
				SWT.FLAT | SWT.BORDER);
		try {
			Method values = enumClazz.getMethod("values", null);
			for (Object option : (Object[])values.invoke(null, null)) {
				combo.add(option.toString());
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
		bindingContext.bindValue(SWTObservables.observeSelection(combo),
				BeansObservables.observeValue(realm, fieldObject, property),
				null, null);
		/**/
	}

	private void createDateField(Composite parent, String fieldLabel, Object fieldObject,
			String property) {
		Label label = toolkit.createLabel(parent, fieldLabel);
		label.setLayoutData(new TableWrapData(TableWrapData.RIGHT));
		CDateTime date = new CDateTime(parent, CDT.BORDER | CDT.DATE_LONG | CDT.DROP_DOWN);

//		date.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
/*		bindingContext.bindValue(SWTObservables.observeText(date, SWT.Modify),
				BeansObservables.observeValue(realm, fieldObject, property),
				null, null);
				/**/
	}


	private void createButtonBar() {
		final Composite buttomBar = toolkit.createComposite(form, SWT.NONE);
		buttomBar.setLayout(new RowLayout(SWT.HORIZONTAL));
		createSaveButton(buttomBar);
	}

	private void createSaveButton(Composite buttomBar) {
		// if (model.canCreateNew()) {
		final Button button = toolkit.createButton(buttomBar, "Save", SWT.PUSH);
		button.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				try {
					model.save();
				} catch (OperationNotSupportedException ex) {
					ex.printStackTrace();
				}
			}
		});
		// }
	}

	
	private void createOperationLink(Composite parent, final Operation operation, final Object obj) {
		TableWrapData gd = new TableWrapData();
		gd.colspan = 1;
		Hyperlink link = toolkit.createHyperlink(parent, operation.getLabel(),
				SWT.WRAP);
		link.addHyperlinkListener(new HyperlinkAdapter() {
			@Override
			public void linkActivated(HyperlinkEvent e) {
				try {
					System.out.println("Operation " + operation.getName() + " called!!");
					operation.invoke(obj, null);
				} catch (InvocationTargetException ex) {
					MessageBox mb = new MessageBox(getSite().getShell(), SWT.ICON_ERROR);
			        mb.setText("Exception");
			        mb.setMessage(ex.getTargetException().getMessage());
			        int val = mb.open();
			    } catch (Exception ex) {
					ex.printStackTrace();
				} 
			}
		});
		link.setLayoutData(gd);
	}
	
	

	@Override
	public void setFocus() {
		form.setFocus();
	}

	@Override
	public void dispose() {
		toolkit.dispose();
		super.dispose();
	}

} // @jve:decl-index=0:visual-constraint="10,10,506,227"
